import torch
import pandas as pd            #c处理csv文件
import matplotlib.pyplot as plt#数据可视化
from torch import nn           #借助已有模型
import tkinter as tk           #应用端可视化
from tkinter import messagebox

#准备数据,pd库读取csv文件
data=pd.read_csv('synthetic_weather_data.csv')#类似于excel
features=data[['tmax','precipitation','wind']].values#双括号表示选择多列
labels=data['tmax_next_day'].values#.values转为numpy

#转化为tensor张量，预备放入CPU中计算
X=torch.tensor(features,dtype=torch.float32)
y=torch.tensor(labels,dtype=torch.float32).view(-1,1)

#打乱数据
indices=torch.randperm(len(X))#包含随机索引的一维张量（0——LEN-1）
X=X[indices]#打乱
y=y[indices]
train_size=int(0.8*len(X))
X_train,X_test=X[:train_size],X[train_size:]
y_train,y_test=y[:train_size],y[train_size:]

#归一化，将数据转化为均值为0，标准差为1的分布
mean = X_train.mean(dim=0)#均值
std = X_train.std(dim=0)#方差
X_train = (X_train - mean) / std
X_test = (X_test - mean) / std

#构建模型
class Net(nn.Module):
    def __init__(self):
        #调用父类 nn.Module 的构造函数，确保父类正确初始化。
        super(Net,self).__init__()
        self.linear = nn.Linear(in_features=X_train.shape[1],out_features=1)
#输入特征维度，输出特征维度 (100,3).shape[1]->3
    def forward(self,x):
        return self.linear(x)

model=Net()
#均方损失
Loss=nn.MSELoss()
#梯度下降
trainer = torch.optim.SGD(model.parameters(),lr=0.01)

#训练次数
epochs=500
#记录损失
loss_history=[]

for epoch in range(epochs):
    outputs=model(X_train)
    loss=Loss(outputs,y_train)
    #清空过往梯度
    trainer.zero_grad()
    #反向传播
    loss.backward()
    #更新参数
    trainer.step()
    loss_history.append(loss.item())
    #每五十轮打印一次损失
    if (epoch+1)%50==0:
        print(f"epoch:{epoch+1},loss:{loss.item():.4f}")

#制图
plt.plot(loss_history)
#设置x,y轴标签
plt.xlabel('epoch')
plt.ylabel('loss')
plt.title('Train_loss')
plt.show()

#切换为评估模式
model.eval()
#输入测试数据，得到预测结果
with torch.no_grad():
    y_pred=model(X_test)

#转化为numpy格式，便于绘图
y_true_np=y_test.numpy()
y_pred_np=y_pred.numpy()
#创建散点图
plt.scatter(x=y_true_np,
            y=y_pred_np,
            alpha=0.5,
            )
plt.plot([min(y_true_np), max(y_true_np)],
         [min(y_true_np), max(y_true_np)],
         'r--', lw=2)#红色虚线，线宽为2
plt.xlabel('True Values')
plt.ylabel('Predictions')
plt.title('Prediction vs True Value')
plt.show()

#预测新数据
def create_gui():
    window=tk.Tk()
    window.title("天气预测")
    window.geometry("300x200")
    window.iconbitmap("test.ico")
    window_labels=["今日最高气温(°C):","降水量(mm):","风速(km/h):"]
    entries=[]
    #创建标签
    for i,text in enumerate(window_labels):
        tk.Label(window,text=text).grid(row=i,column=0,padx=5,pady=5)
        entry=tk.Entry(window)
        entry.grid(row=i,column=1,padx=5,pady=5)
        entries.append(entry)

    result_label=tk.Label(window,text="预测明日最高温:")
    result_label.grid(row=4,columnspan=2,pady=10)

    def predict():
        try:
            inputs=[e.get() for e in entries]
            new_data=[float(x) for x in inputs]
            if len(new_data)!=3:
                raise ValueError('需要三个数值')
            input_tensor=torch.tensor(new_data,dtype=torch.float32)
            norm_input_tensor=(input_tensor-mean)/std

            with torch.no_grad():
                prediction=model(norm_input_tensor)
            result_label.config(text=f"预测明日最高气温{prediction.item():.1f}°C")
        except ValueError as e:
            messagebox.showerror("输入错误",str(e))
            for item in entries:
                item.delete(0,tk.END)

    tk.Button(window,text="开始预测",command=predict).grid(row=3,columnspan=2)
    window.mainloop()

if __name__=="__main__":
    create_gui()